#include <cmath>
#include <nice2d/node.h>
#include <nice2d/misc.h>
#include <nice2d/bitmask.h>
#include <nice2d/string.h>

IMPLEMENT_OBJECT(Node,Object)

class NodeData
{
public:
    sf::Transformable transform;
    std::list<NodePointer> children;
    std::weak_ptr<Node> parent;
    NodeRenderPolicy renderOrder = NodeRenderPolicy_ParentFirst;
    bool dirtyPosition = true;
    bool objectRender = true;
    bool visible = true;
    bool renderChildren = true;
    std::list<NodePointer> detachedChildren;

    void removeDetachedChildren()
    {
        std::for_each(detachedChildren.begin(),detachedChildren.end(),[&](NodePointer node)
        {
            children.remove(node);
            node->data->parent.reset();
        });

        detachedChildren.clear();
    }

    void drawChildren(sf::RenderTarget &target, sf::RenderStates states)const
    {
        if(!renderChildren)
            return;

        std::for_each(children.begin(),children.end(),[&target,states](std::shared_ptr<Node> object) {
            if(object->isVisible())
                object->draw(target, states);
        });
    }

    void updateChildren(float deltaTime)
    {
        std::for_each(children.begin(),children.end(), [deltaTime](std::shared_ptr<Node> object) {object->update(deltaTime);});
    }
};

Node::Node():
    data(new NodeData())
{
}

Node::~Node()
{
}

void Node::setPosition(float x, float y)
{
    setPosition(sf::Vector2f(x, y));
}

void Node::setPosition(const sf::Vector2f &position)
{
    if (getPosition() != position) {
        data->transform.setPosition(position);
        data->dirtyPosition = true;
    }
}

sf::Vector2f Node::getPosition() const
{
    return data->transform.getPosition();
}

void Node::move(float dx, float dy)
{
    auto position = getPosition();
    setPosition(position + sf::Vector2f(dx, dy));
}

void Node::move(float distance)
{
    float dx = distance * std::sin(TO_RADIAN * getRotate());
    float dy = distance * std::cos(TO_RADIAN * getRotate());
    move(-dx, dy);
}

void Node::setRotate(float angle)
{
    if (angle != getRotate()) {
        data->transform.setRotation(angle);
        rotateChanged(angle);
        onRotateChanged();
    }
}

float Node::getRotate() const
{
    return data->transform.getRotation();
}

void Node::setScale(float scale)
{
    setScale(sf::Vector2f(scale, scale));
}

void Node::setScale(float scalex, float scaley)
{
    setScale(sf::Vector2f(scalex, scaley));
}

void Node::setScale(const sf::Vector2f &scale)
{
    if (scale != getScale()) {
        data->transform.setScale(scale.x, scale.y);
        scaleChanged(scale);
        onScaleChanged();
    }
}

sf::Vector2f Node::getScale() const
{
    return data->transform.getScale();
}

void Node::setOrigin(float x, float y)
{
    setOrigin(sf::Vector2f(x, y));
}

void Node::setOrigin(const sf::Vector2f &origin)
{
    if (origin != getOrigin()) {
        data->transform.setOrigin(origin);
        originChanged(origin);
        onOriginChanged();
    }
}

sf::Vector2f Node::getOrigin() const
{
    return data->transform.getOrigin();
}

sf::Transform Node::getTransform() const
{
    return data->transform.getTransform();
}

std::weak_ptr<Node> Node::getParent() const
{
    return data->parent;
}

void Node::addChild(NodePointer child)
{
    if (!child)
        return;

    auto weakParent = child->getParent();
    if (!weakParent.expired()) {
        auto childParent = weakParent.lock();
        if (childParent == shared_from_this())
            return;

        childParent->detachChild(child);
        return;
    }

    data->children.push_back(child);
    child->data->parent = std::dynamic_pointer_cast<Node>(shared_from_this());
}

void Node::detachChild(NodePointer child)
{
    auto find = std::find(data->children.begin(),data->children.end(),child);
    if(find != data->children.end())
        data->detachedChildren.push_back(child);
}

void Node::detachChild(std::function<bool (NodePointer)> fn)
{
    if(!fn)
        return;

    visitChild([&](NodePointer node)
    {
        if(fn(node))
            data->detachedChildren.push_back(node);
    });
}

void Node::visitChild(std::function<void (NodePointer)> fn)
{
    std::for_each(data->children.begin(), data->children.end(), fn);
}

void Node::visitSibling(std::function<void (NodePointer)> fn)
{
    if(!data->parent.expired())
    {
        auto function = [&](NodePointer child)
        {
            if(child != shared_from_this())
                fn(child);
        };
        data->parent.lock()->visitChild(function);
    }
}

void Node::acceptNode(NodeVisitor* visitor)
{
    if (!visitor)
        return;

    std::for_each(data->children.begin(),
    data->children.end(), [visitor](std::shared_ptr<Node> object) {
        visitor->visitNode(object);
    });
}

bool Node::process(std::shared_ptr<Message> message)
{
    auto itr = data->children.begin();
    while (itr != data->children.end()) {
        auto child = *itr;
        if (child->isVisible() && child->process(message))
            return true;
        itr ++;
    }
    return Object::process(message);
}

void Node::update(float deltaTime)
{
    if(isRunning())
    {
        Object::update(deltaTime);

        if(data->dirtyPosition)
        {
            positionChanged(getPosition());
            onPositionChanged();
            data->dirtyPosition = false;
        }

        data->updateChildren(deltaTime);
        onUpdateNode(deltaTime);
        data->removeDetachedChildren();
    }
}

void Node::setVisible(bool visible)
{
    if(data->visible != visible)
    {
        data->visible = visible;
        visibleChanged(visible);
        onVisibleChanged();
    }
}

bool Node::isVisible() const
{
    return data->visible;
}

void Node::setRenderChildren(bool render)
{
    data->renderChildren = render;
}

bool Node::isRenderChildren() const
{
    return data->renderChildren;
}

void Node::setRenderPolicy(NodeRenderPolicy order)
{
    data->renderOrder = order;
}

NodeRenderPolicy Node::getRenderOrder() const
{
    return data->renderOrder;
}

void Node::setRenderObject(bool render)
{
    data->objectRender = render;
}

bool Node::isRenderObject() const
{
    return data->objectRender;
}

void Node::draw(sf::RenderTarget &target, sf::RenderStates states) const
{
    if(!isVisible())
        return;

    onDraw(target,states);
}

PTree Node::serialize()
{
    PTree node;
    node.add_child("position",serializeObject(getPosition()));
    node.add("rotate", getRotate());
    node.add_child("scale",serializeObject(getScale()));
    node.add_child("origin",serializeObject(getOrigin()));
    node.add("renderPolicy", toString(getRenderOrder()));
    return node;
}

void Node::deserialize(const PTree &node)
{
    setPosition(deserializeVector2f(node.get_child("position")));
    setRotate(node.get<float>("rotate"));
    setScale(deserializeVector2f(node.get_child("scale")));
    setOrigin(deserializeVector2f(node.get_child("origin")));
    setRenderPolicy(fromString<NodeRenderPolicy>(node.get<std::string>("renderPolicy"),
                                                 NodeRenderPolicy_ParentFirst));
}

void Node::onVisibleChanged()
{
}

void Node::onDraw(sf::RenderTarget &target, sf::RenderStates states) const
{
    states.transform *= getTransform();
    if (data->renderOrder == NodeRenderPolicy_ParentFirst) {
        onDrawNode(target,states);
        data->drawChildren(target,states);
    }
    else
    {
        data->drawChildren(target,states);
        onDrawNode(target, states);
    }
}

void Node::onDrawNode(sf::RenderTarget &target, sf::RenderStates states) const
{
    (void)target;
    (void)states;
}

void Node::onUpdateNode(float deltaTime)
{
    (void)deltaTime;
}

void Node::onPositionChanged()
{
}

void Node::onRotateChanged()
{
}

void Node::onScaleChanged()
{
}

void Node::onOriginChanged()
{
}

NodeVisitor::~NodeVisitor()
{
}

void NodeVisitor::visitNode(NodePointer object)
{
    (void)object;
}


